/**
 * Copyright (c) 2019 Coder League
 * All rights reserved.
 *
 * File：AlipayInterfaceService.java
 * History:
 *         2019年5月28日: Initially created, Chrise.
 */
package club.coderleague.ilsp.service.interfaces;

import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.DefaultSigner;
import com.alipay.api.Signer;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.alipay.api.request.AlipayTradeCreateRequest;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.alipay.api.response.AlipayTradeCreateResponse;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.fasterxml.jackson.databind.ObjectMapper;

import club.coderleague.ilsp.cache.CacheManager;
import club.coderleague.ilsp.common.config.properties.AlipaySettings;
import club.coderleague.ilsp.common.domain.beans.PayOrder;
import club.coderleague.ilsp.common.domain.beans.PaymentInterfaceResponse;
import club.coderleague.ilsp.common.domain.beans.RefundOrder;
import club.coderleague.ilsp.common.domain.beans.SystemConfig;
import club.coderleague.ilsp.common.domain.beans.UserSession;
import club.coderleague.ilsp.common.domain.enums.OrderPaymentMode;
import club.coderleague.ilsp.common.domain.enums.OrderPaymentState;
import club.coderleague.ilsp.entities.Orders;
import club.coderleague.ilsp.service.orders.OrdersService;
import club.coderleague.ilsp.util.CommonUtil;
import club.coderleague.ilsp.util.HttpUtil;

/**
 * 支付宝接口服务。
 * @author Chrise
 */
@Service
public class AlipayInterfaceService extends AbstractInterfaceService {
	private static final Logger LOGGER = LoggerFactory.getLogger(AlipayInterfaceService.class);
	private static final String FORMAT = "json";
	private static final String CHARSET = "UTF-8";
	private static final String SIGN_TYPE = "RSA2";
	private static final String GRANT_TYPE = "authorization_code";
	private static final String TRADE_STATUS_SUCCESS = "TRADE_SUCCESS";
	private static final String KEY_OUT_TRADE_NO = "out_trade_no";
	private static final String KEY_TOTAL_AMOUNT = "total_amount";
	private static final String KEY_SUBJECT = "subject";
	private static final String KEY_BUYER_ID = "buyer_id";
	private static final String KEY_SIGN_TYPE = "sign_type";
	private static final String KEY_SIGN = "sign";
	private static final String KEY_TRADE_NO = "trade_no";
	private static final String KEY_TRADE_STATUS = "trade_status";
	private static final String KEY_GMT_PAYMENT = "gmt_payment";
	private static final String KEY_REFUND_AMOUNT = "refund_amount";
	private static final String KEY_OUT_REQUEST_NO = "out_request_no";
	private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
	private static String notifyResponse = "";
	
	@Autowired
	private AlipaySettings apSettings;
	@Autowired
	private CacheManager cacheManager;
	@Autowired
	private OrdersService ordersService;
	private Signer localSigner;
	
	static {
		notifyResponse = "success";
	}
	
	/**
	 * 初始化（模拟支付宝开放平台）。
	 * @author Chrise 2019年5月28日
	 */
	@PostConstruct
	private void initialize() {
		if (this.apSettings.getWebAuthSimulatorEnabled()) 
			this.localSigner = new DefaultSigner(this.apSettings.getLocalPrivateKey());
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#queryTrade(club.coderleague.ilsp.entities.Orders)
	 */
	@Override
	public PaymentInterfaceResponse queryTrade(Orders order) throws Exception {
		// 检查支付状态
		if (OrderPaymentState.PAID.equalsValue(order.getPaymentstate())) 
			return new PaymentInterfaceResponse(order.getEntityid(), order.getPaymenttradeno(), order.getPaytime());
		
		// 启用本地支付
		if (this.apSettings.getLocalPayEnabled()) 
			return new PaymentInterfaceResponse(order.getEntityid(), String.valueOf(order.getEntityid()), new Date());
		
		// 获取系统配置
		SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
		if (sc == null || !sc.getApswitch()) throw new RuntimeException("The system config not found or alipay api not available.");
		
		// 构造请求参数
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(KEY_OUT_TRADE_NO, String.valueOf(order.getEntityid()));
		params.put(KEY_TRADE_NO, order.getPaymenttradeno());
		
		// 生成请求内容
		String content = new ObjectMapper().writeValueAsString(params);
		
		// 构造支付宝客户端
		AlipayClient client = new DefaultAlipayClient(this.apSettings.getGateway(), sc.getApappid(), sc.getApmerprikey(), FORMAT, CHARSET, sc.getAppubkey(), SIGN_TYPE);
		
		// 构造支付宝交易查询请求
		AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
		request.setBizContent(content);
		
		// 执行请求并验证结果
		AlipayTradeQueryResponse response = client.execute(request);
		if (response.isSuccess()) {
			// 支付成功
			if (TRADE_STATUS_SUCCESS.equals(response.getTradeStatus())) {
				// 解析付款时间
				Date time = response.getSendPayDate();
				if (time == null) time = new Date();
				
				return new PaymentInterfaceResponse(order.getEntityid(), response.getTradeNo(), time);
			}
		} else throw new RuntimeException("The query return failed. -> " + response.getBody());
		
		return null;
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#applyRefund(club.coderleague.ilsp.common.domain.beans.RefundOrder)
	 */
	@Override
	public void applyRefund(RefundOrder order) throws Exception {
		// 验证退款订单
		if (!verifyRefundOrder(order)) throw new RuntimeException("The order is invalid.");
		
		// 获取系统配置
		SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
		if (sc == null || !sc.getApswitch()) throw new RuntimeException("The system config not found or alipay api not available.");
		
		// 构造请求参数
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(KEY_OUT_TRADE_NO, String.valueOf(order.getOrderid()));
		params.put(KEY_REFUND_AMOUNT, order.getRefundtotal());
		params.put(KEY_OUT_REQUEST_NO, order.getRefundtradeno());
		
		// 生成请求内容
		String content = new ObjectMapper().writeValueAsString(params);
		
		if (this.apSettings.getLocalPayEnabled()) {
			// 发送退款消息
			sendRefundMessage(new PaymentInterfaceResponse(order.getOrderid(), order.getRefundtradeno(), new Date()));
		} else {
			// 构造支付宝客户端
			AlipayClient client = new DefaultAlipayClient(this.apSettings.getGateway(), sc.getApappid(), sc.getApmerprikey(), FORMAT, CHARSET, sc.getAppubkey(), SIGN_TYPE);
			
			// 构造支付宝交易退款请求
			AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
			request.setBizContent(content);
			
			// 执行请求并验证结果
			AlipayTradeRefundResponse response = client.execute(request);
			if (!response.isSuccess()) {
				LOGGER.error("Apply refund with alipay returns [{}]", response.getBody());
				throw new RuntimeException("Apply refund with alipay failed.");
			}
			
			// 发送退款消息
			sendRefundMessage(new PaymentInterfaceResponse(order.getOrderid(), order.getRefundtradeno(), response.getGmtRefundPay()));
		}
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#queryRefund(club.coderleague.ilsp.entities.Orders)
	 */
	@Override
	public PaymentInterfaceResponse queryRefund(Orders order) throws Exception {
		// 检查支付状态
		if (OrderPaymentState.REFUNDED.equalsValue(order.getPaymentstate())) 
			return new PaymentInterfaceResponse(order.getEntityid(), order.getRefundtradeno(), order.getRefundtime());
		
		// 启用本地支付
		if (this.apSettings.getLocalPayEnabled()) 
			return new PaymentInterfaceResponse(order.getEntityid(), order.getRefundtradeno(), new Date());
		
		// 获取系统配置
		SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
		if (sc == null || !sc.getApswitch()) throw new RuntimeException("The system config not found or alipay api not available.");
		
		// 构造请求参数
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(KEY_OUT_TRADE_NO, String.valueOf(order.getEntityid()));
		params.put(KEY_OUT_REQUEST_NO, order.getRefundtradeno());
		
		// 生成请求内容
		String content = new ObjectMapper().writeValueAsString(params);
		
		// 构造支付宝客户端
		AlipayClient client = new DefaultAlipayClient(this.apSettings.getGateway(), sc.getApappid(), sc.getApmerprikey(), FORMAT, CHARSET, sc.getAppubkey(), SIGN_TYPE);
		
		// 构造支付宝退款查询请求
		AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
		request.setBizContent(content);
		
		// 执行请求并验证结果
		AlipayTradeFastpayRefundQueryResponse response = client.execute(request);
		if (response.isSuccess()) {
			// 解析退款时间
			Date time = response.getGmtRefundPay();
			if (time == null) time = new Date();
			
			return new PaymentInterfaceResponse(order.getEntityid(), order.getRefundtradeno(), time);
		} else throw new RuntimeException("The query return failed. -> " + response.getBody());
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#getDomain()
	 */
	@Override
	protected String getDomain() {
		return this.apSettings.getDomain();
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#isWebAuthSimulatorEnabled()
	 */
	@Override
	protected boolean isWebAuthSimulatorEnabled() {
		return this.apSettings.getWebAuthSimulatorEnabled();
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#constructRedirectUrl(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	protected String constructRedirectUrl(String redirect, String code, String state) {
		String url = redirect + "?auth_code=" + code + "&state=" + state;
		return url;
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#constructOpenIdResponse(java.util.Map, java.lang.String)
	 */
	@Override
	protected void constructOpenIdResponse(Map<String, Object> response, String openId) {
		try {
			// 构造响应数据
			Map<String, String> data = new HashMap<String, String>();
			data.put("user_id", openId);
			
			// 生成签名
			String content = new ObjectMapper().writeValueAsString(data);
			String sign = this.localSigner.sign(content, SIGN_TYPE, CHARSET);
			
			// 构造响应对象
			response.put("alipay_system_oauth_token_response", data);
			response.put("sign", sign);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#sendPayNotify(java.lang.String, java.lang.String)
	 */
	@Override
	protected void sendPayNotify(String url, String order) {
		try {
			// 构造参数集合
			Map<String, String> params = new HashMap<String, String>();
			params.put(KEY_OUT_TRADE_NO, order);
			params.put(KEY_TRADE_NO, order);
			params.put(KEY_TRADE_STATUS, TRADE_STATUS_SUCCESS);
			params.put(KEY_GMT_PAYMENT, new SimpleDateFormat(DATE_FORMAT).format(new Date()));
			
			// 签名
			Map<String, String> temp = new HashMap<String, String>(params);
			String sign = AlipaySignature.rsaSign(AlipaySignature.getSignContent(temp), apSettings.getLocalPrivateKey(), CHARSET, SIGN_TYPE);
			
			// 签名参数加入参数集合
			params.put(KEY_SIGN_TYPE, SIGN_TYPE);
			params.put(KEY_SIGN, sign);
			
			String result = HttpUtil.post(url, params);
			LOGGER.info("The pay notify send success and result is [{}].", result);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * @see club.coderleague.ilsp.service.interfaces.AbstractInterfaceService#sendRefundNotify(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	protected void sendRefundNotify(String url, String order, String refund) {
		// 支付接口无通知
	}
	
	/**
	 * 获取授权地址。
	 * @author Chrise 2019年5月28日
	 * @param session 会话对象。
	 * @return 授权地址。
	 */
	public String getAuthUrl(HttpSession session) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getApswitch()) return "";
			
			// 缓存时间戳作为校验码
			long timestamp = System.currentTimeMillis();
			session.setAttribute(AUTH_STATE_KEY, timestamp);
			
			cacheAuthCode();	// 缓存授权代码
			
			// 构建授权地址
			String redirect = URLEncoder.encode(this.apSettings.getWebAuthCallbackRequest(), "UTF-8");
			String url = String.format(this.apSettings.getWebAuthCodeRequest(), sc.getApappid(), redirect, timestamp);
			
			return url;
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		
		return null;
	}
	
	/**
	 * 获取开放标识。
	 * @author Chrise 2019年5月28日
	 * @param code 授权代码。
	 * @param state 状态。
	 * @param session 会话对象。
	 * @return 开放标识。
	 */
	public String getOpenId(String code, String state, HttpSession session) {
		Object timestamp = null;
		
		try {
			// 验证授权校验码
			timestamp = session.getAttribute(AUTH_STATE_KEY);
			if (timestamp == null || 
				!timestamp.toString().equals(state) || 
				(System.currentTimeMillis() - ((Long)timestamp).longValue()) > this.apSettings.getWebAuthCallbackTimeout()) return null;
			
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getApswitch()) return null;
			
			// 构造支付宝客户端
			String key = (this.apSettings.getWebAuthSimulatorEnabled()) ? this.apSettings.getLocalPublicKey() : sc.getAppubkey();
			AlipayClient client = new DefaultAlipayClient(this.apSettings.getGateway(), sc.getApappid(), sc.getApmerprikey(), FORMAT, CHARSET, key, SIGN_TYPE);
			
			// 构造支付宝授权请求
			AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
			request.setCode(code);
			request.setGrantType(GRANT_TYPE);
			
			// 执行请求并验证结果
			AlipaySystemOauthTokenResponse response = client.execute(request);
			if (response.isSuccess()) {
				return response.getUserId();
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			if (timestamp != null) session.removeAttribute(AUTH_STATE_KEY);
		}
		
		return null;
	}
	
	/**
	 * 创建交易。
	 * @author Chrise 2019年6月11日
	 * @param order 订单标识。
	 * @param trade 交易号。
	 * @param user 用户会话。
	 * @return 整体成功时返回交易创建结果，交易创建成功保存失败时返回交易号，发生异常时返回空字符串，订单无效时返回null。
	 */
	public Object createTrade(String order, String trade, UserSession user) {
		Map<String, Object> result = new HashMap<String, Object>();
		
		try {
			// 交易已创建未保存
			if (CommonUtil.isNotEmpty(trade)) {
				// 保存交易
				if (!this.ordersService.savePayTrade(Long.valueOf(order), OrderPaymentMode.ALIPAY, trade)) return "";
			}
			
			// 获取支付订单
			PayOrder po = getPayOrder(Long.valueOf(order));
			if (po == null) return null;
			
			// 交易已创建
			if (OrderPaymentMode.ALIPAY.equalsValue(po.getPaymentmode()) && CommonUtil.isNotEmpty(po.getPaymenttradeno())) {
				// 构造交易创建结果
				result.put(KEY_PAY_API, OrderPaymentMode.ALIPAY.getKey());
				if (this.apSettings.getLocalPayEnabled()) {
					// 启动通知发送任务
					startPayNotifySendTask(this.apSettings.getNotifyUrl(), po.getOrderid());
					
					result.put(KEY_TRADE_NO, false);
				} else result.put(KEY_TRADE_NO, po.getPaymenttradeno());
				result.put(KEY_TOTAL_AMOUNT, po.getPaymenttotal());
				
				return result;
			}
			
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getApswitch()) return "";
			
			// 构造请求参数
			Map<String, Object> params = new HashMap<String, Object>();
			params.put(KEY_OUT_TRADE_NO, String.valueOf(po.getOrderid()));
			params.put(KEY_TOTAL_AMOUNT, po.getPaymenttotal());
			params.put(KEY_SUBJECT, po.getOrdername());
			params.put(KEY_BUYER_ID, user.getOpenid());
			
			// 生成请求内容
			String content = new ObjectMapper().writeValueAsString(params);
			
			if (this.apSettings.getLocalPayEnabled()) {
				String tradeNo = String.valueOf(false);
				
				// 保存交易
				if (!this.ordersService.savePayTrade(po.getOrderid(), OrderPaymentMode.ALIPAY, tradeNo)) return tradeNo;
				
				// 启动通知发送任务
				startPayNotifySendTask(this.apSettings.getNotifyUrl(), po.getOrderid());
				
				// 构造交易创建结果
				result.put(KEY_PAY_API, OrderPaymentMode.ALIPAY.getKey());
				result.put(KEY_TRADE_NO, false);
				result.put(KEY_TOTAL_AMOUNT, po.getPaymenttotal());
				
				return result;
			} else {
				// 构造支付宝客户端
				AlipayClient client = new DefaultAlipayClient(this.apSettings.getGateway(), sc.getApappid(), sc.getApmerprikey(), FORMAT, CHARSET, sc.getAppubkey(), SIGN_TYPE);
				
				// 构造支付宝交易创建请求
				AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
				request.setNotifyUrl(this.apSettings.getNotifyUrl());
				request.setBizContent(content);
				
				// 执行请求并验证结果
				AlipayTradeCreateResponse response = client.execute(request);
				if (response.isSuccess()) {
					String tradeNo = response.getTradeNo();
					
					// 保存交易
					if (!this.ordersService.savePayTrade(po.getOrderid(), OrderPaymentMode.ALIPAY, tradeNo)) return tradeNo;
					
					// 构造交易创建结果
					result.put(KEY_PAY_API, OrderPaymentMode.ALIPAY.getKey());
					result.put(KEY_TRADE_NO, tradeNo);
					result.put(KEY_TOTAL_AMOUNT, po.getPaymenttotal());
					
					return result;
				}
				LOGGER.error("Create trade with alipay returns [{}]", response.getBody());
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		
		return "";
	}
	
	/**
	 * 处理交易通知。
	 * @author Chrise 2019年6月13日
	 * @param request 请求对象。
	 * @param response 响应对象。
	 */
	public void handleTradeNotify(HttpServletRequest request, HttpServletResponse response) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || !sc.getApswitch()) {
				response.getWriter().write(notifyResponse);	// 通知正确处理
				return;
			}
			
			// 构造参数集合
			Map<String, String> params = new HashMap<String, String>();
			Iterator<Entry<String, String[]>> it = request.getParameterMap().entrySet().iterator();
			while (it.hasNext()) {
				Entry<String, String[]> e = it.next();
				
				// 加入参数集合
				params.put(e.getKey(), e.getValue()[0]);
			}
			
			// 验签
			Map<String, String> temp = new HashMap<String, String>(params);
			String key = (this.apSettings.getLocalPayEnabled()) ? this.apSettings.getLocalPublicKey() : sc.getAppubkey();
			boolean valid = AlipaySignature.rsaCheckV1(temp, key, CHARSET, SIGN_TYPE);
			
			if (valid) {	// 验签成功
				Long order = Long.valueOf(params.get(KEY_OUT_TRADE_NO));
				String trade = params.get(KEY_TRADE_NO);
				String status = params.get(KEY_TRADE_STATUS);
				
				if (TRADE_STATUS_SUCCESS.equals(status)) {	// 交易成功
					try {
						// 解析付款时间
						Date time = null;
						String gmt = params.get(KEY_GMT_PAYMENT);
						if (CommonUtil.isEmpty(gmt)) time = new Date();
						else time = new SimpleDateFormat(DATE_FORMAT).parse(gmt);
						
						// 通知支付成功
						PaymentInterfaceResponse pir = new PaymentInterfaceResponse(order, trade, time);
						this.ordersService.updateCompletePayment(pir);
					} catch (Exception e) {
						LOGGER.error("The payment complete handle occur exception -> " + e.getMessage(), e);
					}
				} else LOGGER.error("The trade [{}] not success with status [{}] for order [{}].", trade, status, order);
				
				// 通知正确处理
				response.getWriter().write(notifyResponse);
			} else LOGGER.error("The signature verify failed for [{}]", params);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 将开放标识写入响应（模拟支付宝开放平台）。
	 * @author Chrise 2019年5月28日
	 * @param code 授权代码。
	 * @param response 响应对象。
	 */
	public void writeToResponse(String code, HttpServletResponse response) {
		try {
			Map<String, Object> openId = getOpenId(code);
			response.getWriter().print(new ObjectMapper().writeValueAsString(openId));
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
}
